/*
 * Archivo: printf.c
 * -----------------
 * Extraido de http://www.stanford.edu/class/cs140...s/pintos.tar.gz
 * y modificado para que las salidas sean solamente invocaciones a putchar.
 */

#include "stdio.h"
#include "stdarg.h"
#include "ctype.h"

#define MAX_DIGITS 12

typedef struct {
	enum {
		NORMAL,
		FLAG,
		WIDTH,
		PRECISION,
		TYPE
	} state; /* Estado de la maquina de estados de conversin */
	enum
	{
		MINUS = 1 << 0,         /* '-' */
		PLUS = 1 << 1,          /* '+' */
		SPACE = 1 << 2,         /* ' ' */
		POUND = 1 << 3,         /* '#' */
		ZERO = 1 << 4,          /* '0' */
		GROUP = 1 << 5          /* '\'' */
	} flags;	/* Flags */
	int width; 	/* Ancho mnimo de campo */
	int precision; 	/* Presicin numrica (-1 indica ninguna) */
	enum {
		CHAR = 1,               /* hh */
		SHORT = 2,              /* h */
		INT = 3,                /* (none) */
		INTMAX = 4,             /* j */
		LONG = 5,               /* l */
		LONGLONG = 6,           /* ll */
		PTRDIFFT = 7,           /* t */
		SIZET = 8               /* z */
	} type; 	/* Modicadores del tipo del argumento */
	va_list args;	/* Lista de argumentos a levantar */
	const char *s; 	/* Puntero a la posicin del ltimo '%' */
} vprintf_conversion_t;

typedef int (*vprintf_trans_fn)(vprintf_conversion_t *c, const char *s);
typedef int (*vprintf_format_fn)(vprintf_conversion_t *c, const char *s);

static int vprintf(const char *template, va_list args);
static int reset_conversion(vprintf_conversion_t *c);
static int vprintf_normal_trans(vprintf_conversion_t *c, const char *s);
static int vprintf_flag_trans(vprintf_conversion_t *c, const char *s);
static int vprintf_width_trans(vprintf_conversion_t *c, const char *s);
static int vprintf_precision_trans(vprintf_conversion_t *c, const char *s);
static int vprintf_type_trans(vprintf_conversion_t *c, const char *s);
static int format_char(vprintf_conversion_t *c, const char *s);
static int format_int(vprintf_conversion_t *c, const char *s);
static int format_oct_int(vprintf_conversion_t *c, const char *s);
static int format_pointer(vprintf_conversion_t *c, const char *s);
static int format_string(vprintf_conversion_t *c, const char *s);
static int format_uint(vprintf_conversion_t *c, const char *s);
static int format_hex_int(vprintf_conversion_t *c, const char *s);
static int format_uint_wrap(vprintf_conversion_t *c, unsigned int n,
		int base, int sign);
static int putcn(char c, unsigned int count);


int
printf(const char *fmt, ...)
{
	va_list args;
	int retval;

	va_start(args, fmt);
	retval = vprintf(fmt, args);
	va_end(args);

	return retval;
}

static int
vprintf(const char *template, va_list args)
{
	int i, sum;
	vprintf_conversion_t c;
	vprintf_trans_fn transitions[5];

	transitions[0] = vprintf_normal_trans;
	transitions[1] = vprintf_flag_trans;
	transitions[2] = vprintf_width_trans;
	transitions[3] = vprintf_precision_trans;
	transitions[4] = vprintf_type_trans;

	/* Inicializacin de la estructura de conversin */
	c.args = args;
	reset_conversion(&c);

	for (i = 0, sum = 0; template[i] != '\0'; i++)
		sum += (*(transitions[c.state]))(&c, &(template[i]));

	return sum;
}

static int
reset_conversion(vprintf_conversion_t *c)
{
	c->state = NORMAL;
	c->flags = 0;
	c->width = 0;
	c->precision = -1;
	c->type = 0;
	return 1;
}

static int
vprintf_normal_trans(vprintf_conversion_t *c, const char *s)
{

	if (*s == '%')
	{
		c->state = FLAG;
		c->s = s;
	}
	else
	{
		putchar((int) *s);
		return 1;
	}

	return 0;
}

static int
vprintf_flag_trans(vprintf_conversion_t *c, const char *s)
{
	/* No se usa switch por posibles problemas de compilacin */
	if (*s == '-')
		c->flags |= MINUS;
	else if (*s == '+')
		c->flags |= PLUS;
	else if (*s == ' ')
		c->flags |= SPACE;
	else if (*s == '0')
		c->flags |= ZERO;
	else if (*s == '#')
		c->flags |= POUND;
	else
	{
		c->state = WIDTH;
		return vprintf_width_trans(c, s);
	}

	return 0;
}

static int
vprintf_width_trans(vprintf_conversion_t *c, const char *s)
{
	if (isdigit(*s))
	{
		c->width = 10 * c->width + (*s - '0');
	}
	else if (*s == '.')
	{
		c->precision = 0;
		c->state = PRECISION;
	}
	else
	{
		c->state = TYPE;
		return vprintf_type_trans(c, s);
	}

	return 0;
}

static int
vprintf_precision_trans(vprintf_conversion_t *c, const char *s)
{
	if (isdigit(*s))
	{
		c->precision = 10 * c->precision + (*s - '0');
	}
	else
	{
		c->state = TYPE;
		return vprintf_type_trans(c, s);
	}

	return 0;
}

static int
vprintf_type_trans(vprintf_conversion_t *c, const char *s)
{
	const char *t;
	unsigned int i;
	vprintf_format_fn fn;

	i = *s;
	if (islower(i))
	{
		/* No se usa switch por posibles problemas de compilacin */
		if (i == 'c')
			fn = format_char;
		else if (i == 'd' || i == 'i')
			fn = format_int;
		else if (i == 'o')
			fn = format_oct_int;
		else if (i == 'p')
			fn = format_pointer;
		else if (i == 's')
			fn = format_string;
		else if (i == 'u')
			fn = format_uint;
		else if (i == 'x')
			fn  = format_hex_int;
		else
			fn = NULL;

		if (fn != NULL)
			return (*fn)(c, s);
	}
	else if (isupper(i))
	{
		if (i == 'X')
			fn  = format_hex_int;
		else
			fn = NULL;

		if (fn != NULL)
			return (*fn)(c, s);
	}
	else if (i == '%')
	{
		putchar('%');
		reset_conversion(c);
		return 1;
	}

	/* si no es un template reconocido, imprimir lo que haba */
	for (t = c->s; t <= s; t++)
		putchar(*t);


	reset_conversion(c);
	return (s - c->s);
}


/* Maneja %c */
static int
format_char(vprintf_conversion_t *c, const char *s)
{
	char ch;

	ch = va_arg(c->args, int);
	putchar(ch);
	reset_conversion(c);

	return 1;
}

/* Maneja %i, %d */
static int
format_int(vprintf_conversion_t *c, const char *s)
{
	int n;

	n = va_arg(c->args, int);

	return format_uint_wrap(c, (n < 0 ? -n : n), 10, (n < 0 ? -1 : 1));
}


/* Maneja %o */
static int
format_oct_int(vprintf_conversion_t *c, const char *s)
{
	int n;

	n = va_arg(c->args, unsigned int);

	return format_uint_wrap(c, n, 8, 0);
}

/* Maneja %p */
static int
format_pointer(vprintf_conversion_t *c, const char *s)
{
	void *p;

	/* formato para punteros tipo "%#x" */
	p = va_arg (c->args, void *);
	c->flags = POUND;

	return format_uint_wrap(c, (unsigned int) p, 16, 0);
}

/* Maneja %s */
static int
format_string(vprintf_conversion_t *c, const char *s)
{
	char *str;
	unsigned int i, len;
	int sum;

	/* tomar el string y validar que no sea NULL */
	str = va_arg(c->args, char *);
	if (str == NULL)
		str = "(null)";
	/* strnlen */
	sum = 0;
	for (len = 0; len < c->precision && str[len] != '\0'; len++)
		;
	/* relleno por alineacin derecha */
	if (c->width > len  && (c->flags & MINUS) == 0)
		sum += putcn(' ', c->width - len);
	/* output */
	for (i = 0; str[i] != '\0'; i++)
		putchar(str[i]);

	sum += i;
	/* relleno por alineacin izquierda */
	if (c->width > len  && (c->flags & MINUS) != 0)
		sum += putcn(' ', c->width - len);

	reset_conversion(c);
	return sum;
}

/* Maneja %u */
static int
format_uint(vprintf_conversion_t *c, const char *s)
{
	int n;

	n = va_arg(c->args, unsigned int);

	return format_uint_wrap(c, n, 10, 0);
}

/* Maneja %x */
static int
format_hex_int(vprintf_conversion_t *c, const char *s)
{
	int n;

	n = va_arg(c->args, unsigned int);

	return format_uint_wrap(c, n, (isupper(*s) ? 17 : 16), 0);
}

static int
format_uint_wrap(vprintf_conversion_t *c, unsigned int n, int base, int sign)
{
	char buffer[MAX_DIGITS];
	char digits[] = "0123456789abcdef";
	unsigned int i, j, len;
	int x, pad, upper, d, sum;

	/* determinar signo */
	if (c->flags & PLUS)
		sign = (sign < 0) ? '-' : '+';
	else if (c->flags & SPACE)
		sign = (sign < 0) ? '-' : ' ';
	else
		sign = (sign < 0) ? '-' : 0;

	/* validar base */
	upper = 0;
	if (base > 16)
	{
		upper = 1;
		base = 16;
	}

	/* determinar si usar "0x" o "0X" */
	if ((c->flags & POUND) && n)
		x =  (upper ? 'x' : 'X');
	else
		x = 0;

	/* llenar buffer de dgitos en orden inverso al de impresin */
	i = 0;
	j = n;
	do
	{
		d = digits[j % base];
		buffer[i++] = (upper ? toupper(d) : d);
		j /= base;
	} while (j > 0);

	/* contar relleno necesario */
	len = (c->precision > 0 && i < c->precision) ? c->precision : i;
	pad = c->width - len - (x ? 2 : 0) - (sign != 0);
	if (pad < 0)
		pad = 0;

	/* output */
	sum = 0;
	if ((c->flags & (MINUS | ZERO)) == 0) /* relleno por alineacin der. */
		sum += putcn(' ', pad);
	if (sign) /* imprimir signo si corresponde */
	{
		putchar(sign);
		sum++;
	}
	if (x) /* imprimir "0x" o "0X" si corresponde */
	{
		putchar('0');
		putchar(x);
		sum += 2;
	}
	if (c->flags & ZERO) /* relleno con ZERO */
		sum += putcn('0', pad);
	if (len > i) /* relleno por precisin */
		sum += putcn('0', len - i);
	while (i > 0) /* imprimir dgitos del buffer */
	{
		putchar(buffer[--i]);
		sum++;
	}
	if (c->flags & MINUS) /* relleno con MINUS (alineacin izq.) */
		sum += putcn(' ', pad);

	reset_conversion(c);
	return sum;
}

static int
putcn(char c, unsigned int count)
{
	unsigned int i;

	for (i = 0; i < count; i++)
		putchar(c);

	return count;
}

